package org.xqp.user.service.impl;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.xqp.basic.exception.MyException;
import org.xqp.basic.util.HttpClientUtils;
import org.xqp.basic.util.JsonResult;
import org.xqp.basic.util.MD5Utils;
import org.xqp.basic.util.StrUtils;
import org.xqp.user.constant.UserConstant;
import org.xqp.user.domain.Logininfo;
import org.xqp.user.domain.User;
import org.xqp.user.domain.WxUser;
import org.xqp.user.dto.LoginDto;
import org.xqp.user.mapper.LogininfoMapper;
import org.xqp.user.mapper.UserMapper;
import org.xqp.user.mapper.WxUserMapper;
import org.xqp.user.service.ILoginService;



import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements ILoginService {

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private WxUserMapper wxUserMapper;

    /**
     * 用户登录
     * @param loginDto
     * @return
     */
    @Override
    public Map<String, Object> account(LoginDto loginDto) {
        // 1.非空判断
        if(StringUtils.isEmpty(loginDto.getUsername()) || StringUtils.isEmpty(loginDto.getPassword()) || StringUtils.isEmpty(loginDto.getType())){
            throw new MyException("用户名,密码不能为空");
        }
        // 2.根据前台传过来的登录信息判断用户是否存在
        Logininfo logininfo = logininfoMapper.findByUsernameAndType(loginDto);
        if (logininfo==null){
            throw new MyException("用户不存在");
        }
        // 3.判断密码是否正确  （加密加盐）
        String password = logininfo.getPassword(); // 从数据库查到的对象中获取到密码
        String salt = logininfo.getSalt(); // 从数据库查到的对象中获取盐值
        String md5Password = MD5Utils.encrypByMd5(loginDto.getPassword() + salt); // 将前端传过来的密码和盐值进行MD5加密再和我们后端获取到的用户密码加密信息比对
        if (!md5Password.equals(password)){
            throw  new MyException("密码错误");
        }
        // 判断该用户有没有被禁用
        if(logininfo.getDisable()==0){
            throw new MyException("该用户已被禁用");
        }
        // 4.如果没问题将用户存入resid
        String token = UUID.randomUUID().toString(); // 创建一个随机数当做  key

        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);   // 将 logininfo对象存入redis中

        Map<String, Object> map = new HashMap<>();
        // map中应该存token， 用户信息
        map.put("token",token); // 将存入redis中的key方入map中传给前端
        //  不能让前端看到用户机密信息
        logininfo.setSalt(null);
        logininfo.setPassword(null);
        map.put("logininfo", logininfo); // 将logininfo对象放入map中传给前端
        return map;
    }

    /**
     * 微信登录流程
     * @param params
     * @return
     *
     */
    @Override
    public JsonResult wechat(Map<String, String> params) {
        // 获取前端传过来的code
        String code = params.get("code"); // 获取到我们扫描微信二维码，微信返回给我们的code
        // 判断code是否为空
        if(!StringUtils.hasLength(code)){ // 认为是空，返回true
            throw new MyException("授权码不存在");
        }

        //  通过扫描二维码，微信返回给我们的code获取token（令牌）  获取的是json格式的字符串
        String strObj = HttpClientUtils.httpGet(UserConstant.TOKEN_BY_CODE
                .replace("APPID", UserConstant.APPID)  // 将APPID替换掉
                .replace("SECRET", UserConstant.SECRET) // 将SECRET替换掉
                .replace("CODE", code)); // 将CODE替换掉   将这三个参数替换掉后组成url地址发送http请求后获取到json格式字符串我们需要的令牌就在这个字符串里面

        // 把json格式的字符串转成json对象
        JSONObject jsonObject = JSONObject.parseObject(strObj);
        // 获取到token  令牌
        String accessToken = jsonObject.getString("access_token");
        // 获取到openId  openId可以理解成你扫描的微信账号的唯一id
        String openId = jsonObject.getString("openid");

        // 根据openId查询wxUser表
        WxUser wxUser = wxUserMapper.loadByOpenId(openId);
        // 判断wxUser是否为空，如果不为空，并且user_id存在 免密登录
        if(wxUser!=null && wxUser.getUser_id()!=null){  // 免密登录
            // 登录成功  向redis中logininfo保存信息。
            // 通过user_id 查询logininfo
            Logininfo logininfo = logininfoMapper.findByUserId(wxUser.getUser_id());

            // 如果没有问题，把用户信息存入redis中
            String token = UUID.randomUUID().toString(); // 获取到随机的key
            //  对象存储到redis，需要序列化
            redisTemplate.opsForValue().set(token, // 把token作为key  logininfo用户信息作为value存入redis中并设置过期时间
                    logininfo,
                    30,
                    TimeUnit.MINUTES);

            Map<String, Object> map = new HashMap<>(); //创建一个map分呗装key和value
            // map中应该存token， 用户信息
            map.put("token",token);

            //  不能让前端看到用户机密信息
            logininfo.setSalt(null);
            logininfo.setPassword(null);
            map.put("logininfo", logininfo);

            return JsonResult.me().setResultObj(map); // 将装有redis服务器的 key 和value用户信息的map传给前端

        }else{ // 以前没有扫描过
            String param = "?accessToken="+accessToken+"&openId="+openId; // 将 微信返回给我们的accessToken，openId返回给前端   这样拼接视为了方便前端使用
            return JsonResult.me().setMsg("没有绑定过").setResultObj(param); // 返回给前端
        }
    }


    /**
     * 绑定流程
     * @param params
     * @return
     */
    @Override
    public Map<String, Object> binder(Map<String, String> params) {
        /**
         * 1.非空校验 前端绑定的四个参数都不能为空
         */
        String phone = params.get("phone"); // 前端传过来电话号码
        String verifyCode = params.get("verifyCode");  // 前端传过来的验证码
        String accessToken = params.get("accessToken"); // 授权码
        String openId = params.get("openId"); // 前端传过来的openId
        if(!StringUtils.hasLength(phone) // 非空判断
                ||!StringUtils.hasLength(verifyCode)
                ||!StringUtils.hasLength(accessToken)
                ||!StringUtils.hasLength(openId)){
            throw new MyException("参数不能为空");
        }
        /**
         *  2.验证码是否过期
         */
        Object o = redisTemplate.opsForValue().get(UserConstant.BINDER_PREFIX + phone);
        if (o==null){
            throw new MyException("验证码已过期，请重新获取");
        }
        // 第五步判断前端输入的验证码和后端生成的验证码是否一致
        String redisCode = o.toString().split(":")[1]; //  获取存在redis中的验证码     将o转换为String类型然后以：截断为数组获取其中的验证码
        if(!redisCode.equalsIgnoreCase(verifyCode)){ // equalsIgnoreCase 这个equals比较是不区分大小写
            throw new MyException("验证码错误，请重新输入");
        }
        /**
         * 3.手机是否已经绑定
         */
        User user = userMapper.querryByPhone(phone); // 将前端传过来的电话号码传入数据库查询电话号码是否已经绑定
        User puser = null;
        Logininfo logininfo = null;
        if (user!=null){ // 如果满足就是该电话号码已经绑定
            puser = user;
            // 根据user表中的logininfo_di获取到logininfo对象
            logininfoMapper.findByUserId(puser.getId());
        }else { // 这里就是没绑定
            // 生成user对象，logininfo对象
            puser = initUser(phone); // 获取user对象
            logininfo = initLogininfo(puser); // 获取logininfo对象
            // 和以前一样先保存logininfo对象并返回自增主键
            logininfoMapper.add(logininfo);
            // 将logininfo中的id存入puser中
            puser.setLogininfo_id(logininfo.getId());
            userMapper.add(puser); // 保存user
        }
        // 不管你以前有没有绑定，都要有这个wxuser
        // 我们是要通过token令牌和openid发送请求到去获取到微信用户id，我们先将token和openid放入url请求中
        String wxUserUrl = UserConstant.WX_USER
                .replace("ACCESS_TOKEN", accessToken)
                .replace("OPENID", openId);
        // 根据url发送请求 获取到微信用户信息
        String wxUserObj =  HttpClientUtils.httpGet(wxUserUrl);
        WxUser wxUser = initWxUser(wxUserObj,puser); // 获取wxuser对象
        wxUserMapper.add(wxUser); // 存入wxuser对象

        // 将登陆信息存入redis中，并且返回map给前端
        // 如果没有问题，把用户信息存入redis中
        String token = UUID.randomUUID().toString(); // 获取到随机的key
        //  对象存储到redis，需要序列化
        redisTemplate.opsForValue().set(token, // 把token作为key  logininfo用户信息作为value存入redis中并设置过期时间
                logininfo,
                30,
                TimeUnit.MINUTES);

        Map<String, Object> map = new HashMap<>(); //创建一个map分别装key和value
        // map中应该存token， 用户信息
        map.put("token",token);

        //  不能让前端看到用户机密信息
        logininfo.setSalt(null);
        logininfo.setPassword(null);
        map.put("logininfo", logininfo);

        return map; // 将装有redis服务器的 key 和value用户信息的map传给前端
    }

    /**
     * 获取微信对象信息
     * @param wxUserObj
     * @param puser
     * @return
     */
    private WxUser initWxUser(String wxUserObj, User puser) {
        JSONObject jsonObject = JSONObject.parseObject(wxUserObj);
        WxUser wxUser = new WxUser();
        wxUser.setOpenid(jsonObject.getString("openid"));
        wxUser.setNickname(jsonObject.getString("nickname"));
        wxUser.setSex(jsonObject.getInteger("sex"));
        wxUser.setAddress(null);
        wxUser.setHeadimgurl(jsonObject.getString("headimgurl"));
        wxUser.setUnionid(jsonObject.getString("unionid"));
        wxUser.setUser_id(puser.getId());
        return wxUser;
    }

    /**
     * 根据user获取Logininfo
     * @param user
     * @return
     */
    private Logininfo initLogininfo(User user) {
        Logininfo logininfo = new Logininfo();
        BeanUtils.copyProperties(user, logininfo);
        logininfo.setType(1);
        return logininfo;
    }

    /**
     * 通过手机获取user对象
     * @param phone
     * @return
     */
    private User initUser(String phone) {
        User user = new User();
        user.setUsername(phone);
        user.setPhone(phone);
        user.setState(1);
        String salt = StrUtils.getComplexRandomString(32); // 随机数生成盐值
        user.setSalt(salt); // 设置盐值
        String password = MD5Utils.encrypByMd5(1 + salt);// 加密加盐   将密码和盐值字符串拼接后在进行MD5加密  设置初始密码为1
        user.setPassword(password);
        return user;
    }
}

